using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Framework
{
    public class Scheduler : MonoSingleton<Scheduler>
    {
        public static WaitForEndOfFrame WaitEndOfFrameInstruction = new WaitForEndOfFrame();

        private LinkedList<TimeCounter> _timeCounterList = new LinkedList<TimeCounter>();
        private LinkedList<FrameCounter> _frameCounterList = new LinkedList<FrameCounter>();
        private LinkedList<FrameCounter> _lateFrameCounterList = new LinkedList<FrameCounter>();
        
        public void Update()
        {
            LinkedListNode<FrameCounter> frameCounterNode = _frameCounterList.First;
            while (frameCounterNode != null)
            {
                LinkedListNode<FrameCounter> nextNode = frameCounterNode.Next;
                FrameCounter frameCounter = frameCounterNode.Value;
                if (frameCounter.IsUnused() || frameCounter.Update())
                {
                    _frameCounterList.Remove(frameCounterNode);
                }
                frameCounterNode = nextNode;
            }

            LinkedListNode<TimeCounter> timeCounterNode = _timeCounterList.First;
            while (timeCounterNode != null)
            {
                LinkedListNode<TimeCounter> nextNode = timeCounterNode.Next;
                TimeCounter timeCounter = timeCounterNode.Value;
                if (timeCounter.IsUnused() || timeCounter.Update())
                {
                    _timeCounterList.Remove(timeCounterNode);
                }
                timeCounterNode = nextNode; 
            }
        }
        
        public void LateUpdate()
        {
            LinkedListNode<FrameCounter> lateFrameCounterNode = _lateFrameCounterList.First;
            while (lateFrameCounterNode != null)
            {
                LinkedListNode<FrameCounter> nextNode = lateFrameCounterNode.Next;
                FrameCounter frameCounter = lateFrameCounterNode.Value;
                if (frameCounter.IsUnused() || frameCounter.Update())
                {
                    _lateFrameCounterList.Remove(lateFrameCounterNode);
                }
                lateFrameCounterNode = nextNode;
            }
        }
        
        public void AddFrameListener(int frame, Action<float> listener, int count = 0)
        {
            FrameCounter frameCounter = FindCounter(_frameCounterList, listener);
            if (frameCounter == null)
            {
                frameCounter = new FrameCounter(frame, count, listener);
                _frameCounterList.AddLast(frameCounter);
            }
            if (frameCounter.IsUnused())
            {
                frameCounter.Reset();
            }
            frameCounter.Retain();
        }

        public void RemoveFrameListener(Action<float> listener)
        {
            FrameCounter frameCounter = FindCounter(_frameCounterList, listener);
            if (frameCounter != null)
            {
                frameCounter.Release();
            }
        }
        
        public void AddLateFrameListener(int frame, Action<float> listener, int count = 0)
        {
            FrameCounter frameCounter = FindCounter(_lateFrameCounterList, listener);
            if (frameCounter == null)
            {
                frameCounter = new FrameCounter(frame, count, listener);
                _lateFrameCounterList.AddLast(frameCounter);
            }
            if (frameCounter.IsUnused())
            {
                frameCounter.Reset();
            }
            frameCounter.Retain();
        }

        public void RemoveLateFrameListener(Action<float> listener)
        {
            FrameCounter frameCounter = FindCounter(_lateFrameCounterList, listener);
            if (frameCounter != null)
            {
                frameCounter.Release();
            }
        }

        public void AddTimeListener(float interval, Action<float> listener, int count = 0)
        {
            TimeCounter timeCounter = FindCounter(_timeCounterList, listener);
            if (timeCounter == null)
            {
                timeCounter = new TimeCounter(interval, count, listener);
                _timeCounterList.AddLast(timeCounter);
            }

            if (timeCounter.IsUnused())
            {
                timeCounter.Reset();
            }
            timeCounter.Retain();
        }

        public void RemoveTimeListener(Action<float> listener)
        {
            TimeCounter timeCounter = FindCounter(_timeCounterList, listener);
            if (timeCounter != null)
            {
                timeCounter.Release();
            }
        }

        public void WaitForEndOfFrame(Action listener)
        {
            StartCoroutine(InternalWaitForEndOfFrame(listener));
        }

        private IEnumerator InternalWaitForEndOfFrame(Action listener)
        {
            yield return WaitEndOfFrameInstruction;
            listener.Invoke();
        }

        private T FindCounter<T>(LinkedList<T> counterList, Action<float> callback)
        {
            LinkedListNode<T> node = counterList.First;
            while (node != null)
            {
                if (node.Value.Equals(callback))
                {
                    return node.Value;
                }

                node = node.Next;
            }

            return default;
        }
    }
}